#include "SerialPort.hpp"
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <thread>
#include <string>
#include <csignal>
#include <map>
#include <vector>
#include <cstring>
#include "iochub.h"
#include "SerialPort.hpp"
using namespace std;
#include <locale.h>
using namespace mn::CppLinuxSerial;

enum class FLOWCTRL
{
	None = 0,
	RTS_CTS,
	DTR_DSR,
	XON_XOFF,
};

struct SerialPortInfo
{
	char serialName[128] = "";
	unsigned int Baud = 115200;
	int stopBit = 1;
	int dataBit = 8;
	char checkBit = 'N';
	enum FLOWCTRL flowCtrl = FLOWCTRL::None;
};

struct IocHubInfo
{
	int handle;
	char localId[32];
	char* loaclPassword;
	char* localIp;
	bool serverMode = true;
};
char ServerAddr[20] = "58.213.74.190:38089";
struct SerialPortInfo serialInfo;
struct IocHubInfo iochubInfo;
bool g_threadRun = true;
char lan_id[16] = "";
SerialPort serialPort;
//串口往外发完 并回读到数据耗时
std::map<int, std::string> iochubErr = {
	{0, "open success."},
	{103, "Authentication failed."},
	{104, "Failed to connect to the authentication service."},
	{105, "Service exception (need to retry)."},
	{303, "The current device is not connected for authentication."},
	{304, "The target session device not online"},
	{310, "The target session device NodeID is the local ID."},
	{500, "Socket creation failed."},
	{501, "Socket connection timeout."},
	{503, "Socket read error."},
	{504, "Socket write error."},
};

void usage(void)
{
	const char use[] =
		"  Usage:\n"\
		" -h, --help                          display this message\n"\
		" -c, --comname <COM num>             specify serial name, like:/dev/vserial1\n"\
		" -b, --baud <baud rate>              specify serial baud rate, default: 115200\n"\
		" -d, --databit <date bit>            specify serial databit(5, 6, 7, 8), default: 8\n"\
		" -s, --stopbit <stop bit>            specify serial stopbit(1, 2), default: 1\n"\
		" -v, --checkbit <check bit>          specify serial checkbit(N(None), O(Odd), E(Even)), default: N\n"\
		" -lp, --localpw <xxxxxxxx>           specify the password of the loack session\n"\
		"\n\n"
		"Example: /SerialToIocHub -c /tmp/vserial1  -lp 123456\n"\
		"if success ,Use PuTTY select IocHUB mode to connect Linux in another PC.\n"\
		;
	printf(use);
	exit(0);
}

void signal_handler(int sig)
{
    switch (sig) {
        case SIGINT:
        case SIGTERM:
            serialPort.Close();
            g_threadRun = false;
            std::this_thread::sleep_for(std::chrono::milliseconds(100));
            Session_Close(iochubInfo.handle);
            IoCHub_DeInit();
            break;

        case SIGQUIT:
            std::cout << "正在退出..." << std::endl;
            g_threadRun = false;
            exit(0);
            break;
    }

    exit(0);
}

void printErr()
{
	fprintf(stderr, "%s", iochubErr[IoCHub_GetLastError()].c_str());
}

void IochubEvent(int eventCode, void* eventData)
{
	char TransBuf[20] = "", TempBuf[20] = "";
	char RecvBuf[65550] = "";
	int iocReadlen = 0;
	string s;

	switch (eventCode) {
	case EVENT_RELAY_START:// 启动服务
	case EVENT_ACTIVE_SESSION_OPEN:// 主动打开会话
	case EVENT_UNACTIVE_SESSION_OPEN:// 被动打开会话
		memcpy(TempBuf, ((session_event_t*)eventData)->peerNodeIdStr, 16);
		iochubInfo.handle = ((session_event_t*)eventData)->sessionFd;
		printf("连接成功，对端ID：%s\n", TempBuf);
		break;
	case EVENT_RELAY_STOP:// 停止服务
		printf("Stop Server.\n");
		break;
	case EVENT_SESSION_READ_NOTIFY:// 会话读通知
		break;
	case EVENT_SESSION_CLOSE:// 会话关闭
		printf("Session close.\n");
		break;
	case EVENT_SESSION_TRANSFER_TYPE_CHANGE:// 会话通信模式变化
		printf("Session mode change.\n");
		break;
	case EVENT_SESSION_EXCEPTION:// 会话异常
		printf("Session abnormal.\n");
		printErr();
		break;
	case EVENT_OPERATION_ERR:// IOCHUB错误异常
		printf("EVENT_OPERATION_ERR.\n");
		printErr();
		break;
	}
}

void IocWritedata()
{
	int availLen = 0;
	while(g_threadRun) {
		availLen = serialPort.Available();
		if (availLen > 0) {
			std::vector<uint8_t> data;
			data.reserve(availLen + 7);
			serialPort.ReadBinary(data);
			if (!data.empty()) {
				Session_Write(iochubInfo.handle, (char*)data.data(), data.size());
			}
		} else {
			std::this_thread::sleep_for(std::chrono::milliseconds(0));
		}
	}
}

void IocReaddata()
{
	int iocReadlen = 0;
	std::vector<uint8_t> data;
	data.reserve(65535 + 7);
	while (g_threadRun) {
		if ((iocReadlen = Session_Read(iochubInfo.handle, (char *)data.data(), 65535)) > 0) {
			data.resize(iocReadlen);
			serialPort.WriteBinary(data);
		} else {
			std::this_thread::sleep_for(std::chrono::milliseconds(0));
		}
	}
}

void ScanLan(LAN_SCAN_RESULT_T* pLanDevNodes, int nodeSize)
{
	if (nodeSize > 0) {
		strncpy(lan_id, pLanDevNodes->nodeID, 16);
	}
}

int main(int argc, char* argv[])
{
	int accessMode;
	IOCHUB_ACCESS_MODE_T mode;
	std::thread iocrt;
	std::thread iocwt;
	std::string s;
	char tmp[128] = "123456";
	
	struct sigaction sa;
    sa.sa_handler = signal_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;

    sigaction(SIGINT,  &sa, nullptr);
    sigaction(SIGTERM, &sa, nullptr);
    sigaction(SIGQUIT, &sa, nullptr);
	if (argc == 1) {
		usage();
	}
	for (int i = 1; i < argc; ++i) {
		std::string arg = argv[i];
		if ((arg == "-h" || arg == "--help")) {
			usage();
		}
		else if ((arg == "-c" || arg == "--comname") && i + 1 < argc) {
			strncpy(serialInfo.serialName, argv[i + 1], sizeof(serialInfo.serialName) - 1);
			++i;
		}
		else if ((arg == "-b" || arg == "--baud") && i + 1 < argc) {
			std::string baudRateStr = argv[i + 1];
			serialInfo.Baud = atoi(baudRateStr.c_str());
			++i;
		}
		else if ((arg == "-d" || arg == "--databit") && i + 1 < argc) {
			std::string databitStr = argv[i + 1];
			serialInfo.dataBit = atoi(databitStr.c_str());
			++i;
		}
		else if ((arg == "-s" || arg == "--stopbit") && i + 1 < argc) {
			std::string stopbitStr = argv[i + 1];
			serialInfo.stopBit = atoi(stopbitStr.c_str());
			++i;
		}
		else if ((arg == "-v" || arg == "--checkbit") && i + 1 < argc) {
			serialInfo.checkBit = *argv[i + 1];
			++i;
		}
		else if (arg == "-sv" || arg == "--server") {
			iochubInfo.serverMode = true;
		}
		else if ((arg == "-lp" || arg == "--localpw") && i + 1 < argc) {
			iochubInfo.loaclPassword = argv[i + 1];
			++i;
		}
		else {
			usage();
		}
	}
	// open iocHub
	if (IoCHub_Init(tmp) != 0) {
		printErr();
		goto EXIT;
	}
	IoCHub_RegisterEvent(IochubEvent);

	IOCHUB_INIT_CFG_T iochubCfg;
	iochubCfg.supportTLS = false;
	iochubCfg.supportDTLS = false;
	iochubCfg.supportCompress = false;
	iochubCfg.maxConnSize = 10;
	iochubCfg.recvBufferSize = 1024000;
	iochubCfg.sendBufferSize = 1024000;
	iochubCfg.sendSizePerPackage = 4096;
	iochubCfg.socketWndSize = 200;
	iochubCfg.socketWndTimeout = 100;
	iochubCfg.openDeviceLock = false;
	if (IoCHub_SetCfg(&iochubCfg) != 0) {
		printf("IoCHub set config failed\n");
	}

	if (iochubInfo.localIp != NULL) {
		IoCHub_ScanLanNodes(iochubInfo.localIp, 2000, ScanLan);
		std::this_thread::sleep_for(std::chrono::milliseconds(2000));
		if (strlen(lan_id) == 0) {
			printf("No iochub devices were found in the local area network\n");
			goto EXIT;
		}
	}

	if (IoCHub_Start(ServerAddr, DEFAULT_IOCHUB_START_TIMEOUT, true) != 0) {
		printErr();
		goto EXIT;
	}
	else {
		printf("IocHub start SUCCESS.\n");
		int IDLen = 16;
		if (IoCHub_GetLocalID(iochubInfo.localId, &IDLen) == 0) {
			printf("IocHub local id : %s\n", iochubInfo.localId);
		}
		else {
			printErr();
		}
	}
	accessMode = (iochubInfo.loaclPassword == NULL) ? 0 : 1;
	mode.modeType = accessMode;
	mode.sessionKey = iochubInfo.loaclPassword;
	mode.keySize = accessMode ? strlen(iochubInfo.loaclPassword) : 0;
	if (IoCHub_SetAccessMode(&mode) != 0) {
		printErr();
		goto EXIT;
	}
	// open com
	int index;
	if (strcmp(serialInfo.serialName, "") != 0) {
		serialPort.SetDevice(serialInfo.serialName);
		serialPort.SetTimeout(0);
	}
	else {
		printf("comm name err.\n");
		g_threadRun = false;
		goto EXIT;
	}

	serialPort.Open();
	serialPort.SetBaudRate(serialInfo.Baud);
	switch (serialInfo.checkBit)
	{
		case 'N':
			serialPort.SetParity(mn::CppLinuxSerial::Parity::NONE);
			break;
		case 'O':
			serialPort.SetParity(mn::CppLinuxSerial::Parity::ODD);
			break;
		case 'E':
			serialPort.SetParity(mn::CppLinuxSerial::Parity::EVEN);
			break;
		default:
			break;
	}
	switch (serialInfo.dataBit)
	{
		case 5:
			serialPort.SetNumDataBits(NumDataBits::FIVE);
			break;
		case 6:
			serialPort.SetNumDataBits(NumDataBits::SIX);
			break;
		case 7:
			serialPort.SetNumDataBits(NumDataBits::SEVEN);
			break;
		case 8:
			serialPort.SetNumDataBits(NumDataBits::EIGHT);
			break;	
	}
	switch (serialInfo.stopBit)
	{
		case 1:
			serialPort.SetNumStopBits(mn::CppLinuxSerial::NumStopBits::ONE);
			break;
		case 2:
			serialPort.SetNumStopBits(mn::CppLinuxSerial::NumStopBits::TWO);
			break;
	}

	printf("Processing...\n");
	iocwt = std::thread(IocWritedata);
	iocrt = std::thread(IocReaddata);

EXIT:
	iocrt.join();
	iocwt.join();
	serialPort.Close();
	Session_Close(iochubInfo.handle);
	IoCHub_DeInit();
}